// /src/app/middleware/security.middleware.js
'use strict';

const { logger, color, emailTool } = require('../utils');
const config = require('../config');

// 配置容错：避免配置缺失导致报错
const ALERT_EMAILS = config.environment.alert?.receiveEmails?.split(',').map((email) => email.trim()) || [];
const NODE_ENV = config.environment.nodeEnv || 'development';
const LOGIN_FAIL_LIMIT = config.environment.alert?.loginFailLimit || 5; // 默认阈值5次
const LOGIN_FAIL_EXPIRE = 3600; // 登录失败次数1小时过期（企业级常规配置）

// 内存存储实现（服务重启后丢失，仅开发/测试用）
const loginFailStore = {
    data: {},
    get: (key) => Promise.resolve(this.data[key] || 0),
    incr: (key) => {
        this.data[key] = (this.data[key] || 0) + 1;
        // 首次递增时设置过期时间
        if (this.data[key] === 1) {
            setTimeout(() => delete this.data[key], LOGIN_FAIL_EXPIRE * 1000);
        }
        return Promise.resolve(this.data[key]);
    },
    setExpire: () => Promise.resolve(), // 内存版无需额外设置过期
};

/**
 * 安全告警中间件
 * 核心功能：登录失败次数超限告警、敏感接口访问通知
 * 优化点：去掉async冗余、配置容错、异常兜底、告警信息美化
 */
const securityMiddleware = (req, res, next) => {
    const clientIp = req.ip || req.connection.remoteAddress || '未知IP';
    const path = req.path;
    const method = req.method;

    // 1. 登录失败次数监控（适配登录接口：/api/v1/login POST）
    if (path === '/api/v1/login' && method === 'POST') {
        // 拦截响应，判断登录结果（不破坏原有响应逻辑）
        const originalSend = res.send;
        res.send = function (body) {
            // 用try-catch兜底，避免监控逻辑异常影响响应
            try {
                // 处理响应体：兼容字符串/对象格式，避免JSON解析失败
                let response;
                if (typeof body === 'object' && body !== null) {
                    response = body;
                } else if (typeof body === 'string') {
                    try {
                        response = JSON.parse(body);
                    } catch (parseErr) {
                        // 响应体不是JSON（如HTML错误页），不处理登录失败监控
                        logger.debug(`${color.gray}🔍 登录接口响应非JSON格式，跳过失败次数监控${color.reset}`, {
                            metadata: {},
                        });
                        return originalSend.call(this, body);
                    }
                } else {
                    return originalSend.call(this, body);
                }

                // 登录失败判断（兼容code!==200或msg包含"失败"的场景）
                const isLoginFail = response.code !== 200 || (response.msg && response.msg.includes('失败'));
                if (isLoginFail) {
                    const username = req.body?.username || req.query?.username || '未知账号';
                    const storeKey = `corphr:login_fail:${username}:${clientIp}`;

                    // 递增失败次数（非阻塞，不影响响应速度）
                    loginFailStore
                        .incr(storeKey)
                        .then((failCount) => {
                            // 首次失败时设置过期时间（Redis/内存都生效）
                            if (failCount === 1) {
                                loginFailStore.setExpire(storeKey);
                            }

                            // 打印登录失败日志
                            logger.warn(
                                `${color.yellow}⚠️  登录失败：账号=${username} | IP=${clientIp} | 累计次数=${failCount} | 阈值=${LOGIN_FAIL_LIMIT}次${color.reset}`,
                                { metadata: {} },
                            );

                            // 超过阈值触发安全告警（生产环境）
                            if (failCount >= LOGIN_FAIL_LIMIT && NODE_ENV === 'production' && ALERT_EMAILS.length > 0) {
                                const alertContent = `
                                    <p style="color: #d32f2f; font-size: 16px; font-weight: bold;">🚨 [安全告警] CorpHR账号登录失败次数超限</p>
                                    <p>📅 时间：${new Date().toLocaleString()}</p>
                                    <p>🌍 环境：${NODE_ENV}</p>
                                    <p>👤 账号：${username}</p>
                                    <p>🌐 客户端IP：${clientIp}</p>
                                    <p>🔢 失败次数：${failCount}次（阈值：${LOGIN_FAIL_LIMIT}次）</p>
                                    <p>⏳ 过期时间：${LOGIN_FAIL_EXPIRE / 3600}小时后</p>
                                    <p>⚠️  风险等级：高（可能存在暴力破解、账号盗用风险）</p>
                                    <p>💡 处理建议：</p>
                                    <p>1. 核查该账号是否为本人操作（可联系用户确认）</p>
                                    <p>2. 临时锁定账号（需在业务代码中实现锁定逻辑）</p>
                                    <p>3. 提醒用户修改密码（建议强密码+二次验证）</p>
                                    <p>4. 封禁异常IP（若多次出现同一IP攻击）</p>
                                `;

                                // 给所有告警接收人发送邮件（非阻塞，不影响响应）
                                ALERT_EMAILS.forEach((email) => {
                                    emailTool
                                        .sendNotice(email, '[安全告警] CorpHR账号登录失败次数超限', alertContent)
                                        .catch((mailErr) => {
                                            logger.error(
                                                `${color.red}❌ 登录失败告警邮件发送失败（收件人：${email}）：${mailErr.message}${color.reset}`,
                                                { metadata: {} },
                                            );
                                        });
                                });
                            }
                        })
                        .catch((storeErr) => {
                            // 存储操作失败（如Redis连接异常），仅日志告警，不影响登录流程
                            logger.error(`${color.red}❌ 登录失败次数存储异常：${storeErr.message}${color.reset}`, {
                                metadata: {},
                            });
                        });
                }
            } catch (err) {
                // 监控逻辑自身异常兜底，确保不中断响应
                logger.error(`${color.red}❌ 登录监控逻辑异常：${err.message}${color.reset}`, { metadata: {} });
            }

            // 执行原始响应逻辑，确保前端能正常接收结果
            return originalSend.call(this, body);
        };
    }

    // 2. 敏感接口访问通知（企业级核心操作监控）
    const sensitivePaths = [
        '/api/v1/user/delete',
        '/api/v1/role/update',
        '/api/v1/permission/assign',
        '/api/v1/salary/calculate',
        '/api/v1/employee/transfer',
        '/api/v1/system/setting', // 新增：系统配置修改（敏感操作补充）
    ];

    if (sensitivePaths.includes(path) && method !== 'GET') {
        const userId = req.body?.userId || req.query?.userId || req.params?.userId || '未知用户';
        // 完整请求参数（包含body/query/params，告警信息更全面）
        const requestParams = JSON.stringify({ body: req.body, query: req.query, params: req.params }, null, 2);

        // 打印敏感操作日志
        logger.info(
            `${color.blue}🔒  敏感接口访问：用户=${userId} | IP=${clientIp} | 接口=${path} | 方法=${method}${color.reset}`,
            { metadata: {} },
        );

        // 生产环境发送访问通知（非阻塞）
        if (NODE_ENV === 'production' && ALERT_EMAILS.length > 0) {
            const alertContent = `
                <p style="color: #1976d2; font-size: 16px; font-weight: bold;">🔒 [安全通知] CorpHR敏感接口访问记录</p>
                <p>📅 时间：${new Date().toLocaleString()}</p>
                <p>🌍 环境：${NODE_ENV}</p>
                <p>👤 操作用户：${userId}</p>
                <p>🌐 客户端IP：${clientIp}</p>
                <p>📍 访问接口：${path}</p>
                <p>🔧 请求方法：${method}</p>
                <p>📋 请求参数：<pre style="background: #f5f5f5; padding: 8px; border-radius: 4px;">${requestParams}</pre></p>
                <p>🖥️  客户端信息：${req.headers['user-agent'] || '未知'}</p>
                <p>ℹ️  说明：该操作涉及核心数据/权限变更，建议核查操作合法性</p>
            `;

            ALERT_EMAILS.forEach((email) => {
                emailTool.sendNotice(email, '[安全通知] CorpHR敏感接口访问记录', alertContent).catch((mailErr) => {
                    logger.error(
                        `${color.red}❌ 敏感接口通知邮件发送失败（收件人：${email}）：${mailErr.message}${color.reset}`,
                        { metadata: {} },
                    );
                });
            });
        }
    }

    // 继续执行后续中间件/路由（确保请求流程不中断）
    next();
};

module.exports = securityMiddleware;
